/**
	Pulsar engine. Core.
	Startup initialization functions - windows extension

	Copyright (C) 2013-2014 Dmitry Dolgov

	This program is free software; you can redistribute it and/or modify it under the terms of the
	GNU General Public License as published by the Free Software Foundation; either version 2 of
	the License, or (at your option) any later version.

	This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
	without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
	See the GNU General Public License for more details.
*/

#if !defined(PL_WINDOWS) && !defined(PL_WINDOWS64)
#error "This file can be user for Windows applications only"
#endif

#include <windows.h>
#include <winnt.h>
#include <dbghelp.h>
#include <tlhelp32.h>

#include <pulsar/pulsar.h>
#include <pulsar/src/win/pulsar_intr_win.h>

namespace pl
{

typedef BOOL (WINAPI *MiniDumpWriteDumpCb)(HANDLE, DWORD, HANDLE, MINIDUMP_TYPE, PMINIDUMP_EXCEPTION_INFORMATION, PMINIDUMP_USER_STREAM_INFORMATION, PMINIDUMP_CALLBACK_INFORMATION);

AppOptions appOptions;

static LONG WINAPI _unhandledExceptionFilter(struct _EXCEPTION_POINTERS* exceptionInfo);
static void _dumpMemoryArea(const void* ptr, int offsetDown, int offsetUp, const pl_char* displayName);

static LPTOP_LEVEL_EXCEPTION_FILTER _prevExceptionFilter;
static HINSTANCE _dbgHelpLib = NULL;
static bool _bSetExceptionFilter = false;
static MiniDumpWriteDumpCb _writeDumpProc = NULL;

} // namespace pl


/// Allows to show debugging windows
void pl::StartupFunc::setDebugWindows(pl_uint32 windowMask)
{
	appOptions.windowMask = windowMask;
	return;
}

/// Set common controls
void pl::StartupFunc::setCommonControls(pl_uint32 commonControls)
{
	appOptions.commonControls |= commonControls;
	return;
}

/*
	Allow to trap signals from console application
*/
void pl::StartupFuncBase::enableSignals()
{
	appOptions.bEnableSignals = true;
	return;
}

/*
	Set affinity mask to execute process
*/
void pl::StartupFuncBase::setCpuAffinity(pl_uint32 indexMask)
{
	appOptions.cpuAffinity = indexMask;
	return;
}

/// Set unhandled exception filter
void pl::StartupFunc::registerExceptionFilter()
{
	if (_dbgHelpLib == NULL)
	{
		_dbgHelpLib = LoadLibrary(PL_T("dbghelp.dll"));
		if (_dbgHelpLib != NULL)
		{
			_writeDumpProc = (MiniDumpWriteDumpCb)GetProcAddress(_dbgHelpLib, "MiniDumpWriteDump");
		}
	}

	if (_bSetExceptionFilter)
	{
		return;
	}
	_bSetExceptionFilter = true;
	_prevExceptionFilter = SetUnhandledExceptionFilter(_unhandledExceptionFilter);
	return;
}

/*
	Trap Win32 exceptions
*/
static LONG WINAPI pl::_unhandledExceptionFilter(struct _EXCEPTION_POINTERS* exceptionInfo)
{
	switch (exceptionInfo->ExceptionRecord->ExceptionCode)
	{
		case EXCEPTION_ACCESS_VIOLATION:
		case EXCEPTION_ARRAY_BOUNDS_EXCEEDED:
		case EXCEPTION_INT_DIVIDE_BY_ZERO:
		case EXCEPTION_PRIV_INSTRUCTION:
		case EXCEPTION_IN_PAGE_ERROR:
		case EXCEPTION_ILLEGAL_INSTRUCTION:
		case EXCEPTION_NONCONTINUABLE_EXCEPTION:
		case EXCEPTION_STACK_OVERFLOW:
		case EXCEPTION_INVALID_DISPOSITION:
			break;
		default:
			// All other errors are continuable
			plLogFatal(PL_LOG_NAME, PL_T("Unhandled exeception: ") << (void*)exceptionInfo->ExceptionRecord->ExceptionCode);
			plLogFatal(PL_LOG_NAME, PL_T("   Exception address: ") << exceptionInfo->ExceptionRecord->ExceptionAddress);
			return EXCEPTION_CONTINUE_SEARCH;
	}

	// Dump main registers
	plLogFatal(PL_LOG_NAME, PL_T("Unhandled exeception: ") << (void*)exceptionInfo->ExceptionRecord->ExceptionCode);
	plLogFatal(PL_LOG_NAME, PL_T("   Exception address: ") << exceptionInfo->ExceptionRecord->ExceptionAddress);
	plLogFatal(PL_LOG_NAME, PL_T("   Exception flags: ") << (pl_uint32)exceptionInfo->ExceptionRecord->ExceptionFlags);

	plLogFatal(PL_LOG_NAME, PL_T("Registers information: "));
	plLogFatal(PL_LOG_NAME, PL_T("EAX: ") << (void*)(exceptionInfo->ContextRecord->Eax) << PL_T("; EBX: ") << (void*)(exceptionInfo->ContextRecord->Ebx));
	plLogFatal(PL_LOG_NAME, PL_T("ECX: ") << (void*)(exceptionInfo->ContextRecord->Ecx) << PL_T("; EDX: ") << (void*)(exceptionInfo->ContextRecord->Edx));
	plLogFatal(PL_LOG_NAME, PL_T("ESI: ") << (void*)(exceptionInfo->ContextRecord->Esi) << PL_T("; EDI: ") << (void*)(exceptionInfo->ContextRecord->Edi));
	plLogFatal(PL_LOG_NAME, PL_T("EBP: ") << (void*)(exceptionInfo->ContextRecord->Ebp) << PL_T("; EFL: ") << (void*)(exceptionInfo->ContextRecord->EFlags));
	plLogFatal(PL_LOG_NAME, PL_T("EIP: ") << (void*)(exceptionInfo->ContextRecord->Eip) << PL_T("; ESP: ") << (void*)(exceptionInfo->ContextRecord->Esp));

	// TODO - add floating point registers
	pl::_dumpMemoryArea((const void*)(exceptionInfo->ContextRecord->Eax), 256, 256, PL_T("EAX"));
	pl::_dumpMemoryArea((const void*)(exceptionInfo->ContextRecord->Ebx), 256, 256, PL_T("EBX"));
	pl::_dumpMemoryArea((const void*)(exceptionInfo->ContextRecord->Ecx), 256, 256, PL_T("ECX"));
	pl::_dumpMemoryArea((const void*)(exceptionInfo->ContextRecord->Edx), 256, 256, PL_T("EDX"));
	pl::_dumpMemoryArea((const void*)(exceptionInfo->ContextRecord->Esi), 256, 256, PL_T("ESI"));
	pl::_dumpMemoryArea((const void*)(exceptionInfo->ContextRecord->Edi), 256, 256, PL_T("EDI"));
	pl::_dumpMemoryArea((const void*)(exceptionInfo->ContextRecord->Ebp), 256, 256, PL_T("EBP"));
	pl::_dumpMemoryArea((const void*)(exceptionInfo->ContextRecord->Eip), 256, 256, PL_T("EIP"));
	pl::_dumpMemoryArea((const void*)(exceptionInfo->ContextRecord->Esp), 256, 256, PL_T("ESP"));

	// Walk all modules
	HANDLE hWalkProcesses = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, GetProcessId(pl::App::app->getHInstance()));
	if (hWalkProcesses == INVALID_HANDLE_VALUE)
	{
		plLogFatal(PL_LOG_NAME, PL_T("Can't snap modules list information"));
	}
	else
	{
		MODULEENTRY32  moduleInfo = {0};
		moduleInfo.dwSize = sizeof(MODULEENTRY32);

		if (Module32First(hWalkProcesses, &moduleInfo))
		{
			plLogFatal(PL_LOG_NAME, PL_T("Module information: "));
			do
			{
				plLogFatal(PL_LOG_NAME, PL_T("   Module file name: ") << moduleInfo.szExePath);
				plLogFatal(PL_LOG_NAME, PL_T("   Base address: ") << moduleInfo.modBaseAddr << PL_T("; size: ") << (pl_uint32)moduleInfo.modBaseSize);
				plLogFatal(PL_LOG_NAME, PL_T("   Module handle: ") << (void *)moduleInfo.hModule);
			} while (Module32Next(hWalkProcesses, &moduleInfo));
		}
		CloseHandle(hWalkProcesses);
	}

	// Walk all threads
	DWORD processId = GetCurrentProcessId();
	HANDLE hWalkThreads = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, processId);
	if (hWalkThreads == INVALID_HANDLE_VALUE)
	{
		plLogFatal(PL_LOG_NAME, PL_T("Can't snap thread list information"));
	}
	else
	{
		THREADENTRY32  threadInfo  = {0};
		threadInfo.dwSize = sizeof(THREADENTRY32);

		if (Thread32First(hWalkThreads, &threadInfo))
		{
			plLogFatal(PL_LOG_NAME, PL_T("Thread information: "));
			do
			{
				if (threadInfo.th32OwnerProcessID == processId)
				{
					plLogFatal(PL_LOG_NAME, PL_T("   Thread ID: ") << (pl_uint32)threadInfo.th32ThreadID);
					plLogFatal(PL_LOG_NAME, PL_T("   Thread owner ID: ") << (pl_uint32)threadInfo.th32OwnerProcessID);
					plLogFatal(PL_LOG_NAME, PL_T("   Thread priority: ") << (pl_uint32)threadInfo.tpBasePri);
				}
			} while (Thread32Next(hWalkThreads, &threadInfo));
		}
		CloseHandle(hWalkThreads);
	}

	// Log other information
	plLogFatal(PL_LOG_NAME, PL_T("Process handle: ") << GetCurrentProcess() << PL_T("Process id: ") << (pl_uint32)GetCurrentProcessId());
	plLogFatal(PL_LOG_NAME, PL_T("Current thread handle: ") << GetCurrentThread() << PL_T("Current thread id: ") << (pl_uint32)GetCurrentThreadId());

	DWORD_PTR processAffinityMask, systemAffinityMask;
	GetProcessAffinityMask(GetCurrentProcess(), &processAffinityMask, &systemAffinityMask);
	plLogFatal(PL_LOG_NAME, PL_T("Affinity mask process: ") << (pl_uint32)processAffinityMask << PL_T("Affinity mask system: ") << (pl_uint32)systemAffinityMask);
	plLogFatal(PL_LOG_NAME, PL_T("Command Line params: ") << GetCommandLine());

	pl_char fileName[MAX_PATH];
	if (GetModuleFileName((HMODULE)GetCurrentProcess(), fileName, sizeof(fileName)) == 0)
	{
		plLogFatal(PL_LOG_NAME, PL_T("GetModuleFileName(): failed"));
	}
	else
	{
		plLogFatal(PL_LOG_NAME, PL_T("GetModuleFileName(): ") << fileName);
	}

	LPTCH envStrings = GetEnvironmentStrings();
	if (envStrings)
	{
		plLogFatal(PL_LOG_NAME, PL_T("Environment variables:"));
		const pl_char* envStr = envStrings;
		while (*envStr != '\0')
		{
			String<1024> str;

			while (pl_strlen(envStr) > 600)
			{
				str.set(envStr, 600);
				plLogFatal(PL_LOG_NAME, PL_T("   ") << str << PL_T("...->"));
				envStr += 600;
			}
			plLogFatal(PL_LOG_NAME, PL_T("   ") << envStr);
			envStr += pl_strlen(envStr) + 1;
		}

		FreeEnvironmentStrings(envStrings);
		envStrings = NULL;
	}
	else
	{
		plLogFatal(PL_LOG_NAME, PL_T("GetEnvironmentStrings() failed!"));
	}

	// Collect common information
	OSVERSIONINFO  verInfo = {0};

	verInfo.dwOSVersionInfoSize  = sizeof(OSVERSIONINFO);
	if (!GetVersionEx(&verInfo))
	{
		plLogFatal(PL_LOG_NAME, PL_T("GetVersionEx() failed"));
	}
	else
	{
		plLogFatal(PL_LOG_NAME, PL_T("GetVersionEx() information:"));
		plLogFatal(PL_LOG_NAME, PL_T("   System version: ") << (pl_uint32)verInfo.dwMajorVersion << PL_T(".") << (pl_uint32)verInfo.dwMinorVersion);
		plLogFatal(PL_LOG_NAME, PL_T("   Build number: ") << (pl_uint32)verInfo.dwBuildNumber << PL_T("; ") << verInfo.szCSDVersion);
		plLogFatal(PL_LOG_NAME, PL_T("   Computer name: ") << pl::AppBase::appBase->getComputerName());
	}
	SYSTEM_INFO sysInfo = {0};
	GetSystemInfo(&sysInfo);
	plLogFatal(PL_LOG_NAME, PL_T("   Processor Architecture: ") << sysInfo.wProcessorArchitecture);
	plLogFatal(PL_LOG_NAME, PL_T("   Page size: ") << (pl_uint32)sysInfo.dwPageSize);
	plLogFatal(PL_LOG_NAME, PL_T("   Number of processors: ") << (pl_uint32)sysInfo.dwNumberOfProcessors);

	// Pass information about event to subscribers
	pl::ExceptionInfo info;

	// TODO - fill info
	IAppEvents::callOnException(info);

	// TODO return depends of callOnException result

	return EXCEPTION_CONTINUE_SEARCH; // EXCEPTION_EXECUTE_HANDLER EXCEPTION_CONTINUE_EXECUTION
}

/*
*/
static void pl::_dumpMemoryArea(const void* ptr, int offsetDown, int offsetUp, const pl_char* displayName)
{
	pl_intptr baseadr = (pl_intptr)ptr - offsetDown;
	pl_intptr topadr = (pl_intptr)ptr + offsetUp;
	pl::String128 str;

	baseadr &= ~0xF;
	topadr &= ~0xF;

	plLogFatal(PL_LOG_NAME, PL_T("Data segment ") << displayName << PL_T(": ") << ptr);
	while (baseadr < topadr)
	{
		String<16> addrTextPad(int2str<16>(baseadr, 16));

		addrTextPad.fillLeft(PL_T('0'), 10);

		str.set(PL_T("  0x"));
		str << addrTextPad << PL_T(" ");

		if (IsBadReadPtr((void*)baseadr, 16))
		{
			str << PL_T("bad read area");
		}
		else
		{
			for (int i=0; i<16; i++)
			{
				addrTextPad.set(int2str<16>(((pl_uint8*)baseadr)[i], 16));
				addrTextPad.fillLeft(PL_T('0'), 2);
				str << addrTextPad << PL_T(' ');
			}
			for (int i=0; i<16; i++)
			{
				pl_uint8 c = ((pl_uint8*)baseadr)[i];
				str << ((c < 32) ? PL_T('.') : (pl_char)c);
			}
		}

		baseadr += 16;
		plLogFatal(PL_LOG_NAME, str);
	}
	return;
}

//
// End of file 'pulsar_startup_win.cpp'
//
